package czopyk.model.structure;

import java.util.Observable;
import java.util.Observer;

import model.Event;
import model.Time;

import org.apache.log4j.Logger;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import czopyk.model.cost.DistributionId;
import czopyk.model.cost.DistributionManager;
import czopyk.model.request.Request;

public class LoadGenerator implements Observer {

	protected final Logger log = Logger.getLogger("Load generator");

	private int usersCount = 1;
	private DistributionId delayDitributionId;
	private BiMap<Long, Integer> ur = HashBiMap.create();

	private LoadSelector ls;
	private Simulator simulator;
	private DistributionManager distributionManager;

	public void run() {
		log.info("starting load generation - " + usersCount + " user(s)");
		for (int i = 0; i < usersCount; ++i) {
			log.info("initializing user " + i + "...");
			restartUser(i, i * 1000);
		}
	}

	private void restartUser(int i, Integer delay) {
		if (ur.size() < usersCount) {
			final Request r = ls.getRequest();
			ur.put(r.getId(), i);
			
			if (delay != null && delay > 0) {
				Time.registerEvent(new Event(Time.currentTime() + delay) {
					@Override
					public void perform() {
						LoadGenerator.this.perform(r);
					}

					@Override
					public String getName() {
						return "UserRestartDelay";
					}
				});
			} else {
				perform(r);
			}
		}
	}

	public void requestComplete(final Request r) {
		if (!r.isDone()) {
			return;
		}

		final Integer user = ur.get(r.getId());
		log.debug("Request " + r.getId() + " of user " + user + " complete");

		if (user != null) {
			log.debug("user " + user + " holding...");
			Time.registerEvent(new Event(Time.currentTime()
					+ distributionManager.getDistributionDoubleValue(delayDitributionId)) {
				@Override
				public void perform() {
					ur.remove(r.getId());
					restartUser(user, null);
				}

				@Override
				public String getName() {
					return "UserSleepDelay";
				}
			});
		}
	}

	protected void perform(Request r) {
		simulator.perform(r);
	}

	public void addUser() {
		usersCount += 1;
		log.info("adding user " + usersCount);
		restartUser(usersCount, null);
		log.debug("userCount: " + usersCount);
	}

	public void removeUser() {
		if (!canRemoveUser()) {
			throw new IllegalStateException("Cannot remove user.");
		}
		log.debug("removing user...");
		--usersCount;
		log.debug("users count: " + usersCount);
	}

	public boolean canRemoveUser() {
		return usersCount > 1;
	}

	public void setLatency(DistributionId delayId) {
		this.delayDitributionId = delayId;
	}

	public void setSimulator(Simulator simulator) {
		this.simulator = simulator;
		simulator.addObserver(this);
	}

	public void update(Observable o, Object arg) {
		if (arg instanceof Request) {
			requestComplete((Request) arg);
		}
	}

	public void setLoadSelector(LoadSelector ls) {
		this.ls = ls;
	}

	@Override
	public String toString() {
		return "LoadGenerator:\nusersCount = " + usersCount + "\nSimulator: "
				+ (simulator != null ? "set" : "unset") + "\nLoadSelector: "
				+ (ls != null ? "set" : "unset") + "\nLatency: "
				+ (delayDitributionId != null ? "set" : "unset");
	}

	public int getUsersCount() {
		return usersCount;
	}

	public void setUserCount(int count) {
		if (count < 1) {
			throw new IllegalArgumentException("Number of users must be positive.");
		}
		usersCount = count;
	}

	public void setDistributionManager(DistributionManager distributionManager) {
		this.distributionManager = distributionManager;
	}

}
